#include <linux/netdevice.h>
#include <linux/inetdevice.h>/*for in_get_dev*/
#include <net/addrconf.h> /*for in6_get_dev*/
#include <linux/timer.h>

#include "socs/comm/types.h"
#include "socs/mod/netdev_monitor.h"
#include "socs/mod/neigh.h"
#include "socs/mod/multicast.h"

static struct timer_list dev_up_timer;
static char ifname[IFNAMSIZ] = {0};


static int netdev_remove(struct net_device *dev)
{
	log_debug("netdev_remove,name %s", dev->name);
	socs_neigh_localnode_down(dev->name);
	socs_mul_localnode_down(dev->name);
	return 0;
}

static int netdev_ip_remove(struct net_device *dev, void *address, int family)
{
	if (family == IPV4) {
		log_debug("[netdev_ip_remove]locallink_remove name %s ip4 address %pI4c",
				  dev->name, address);
	} else {
		log_debug("[netdev_ip_remove]locallink_remove name %s ip6 address %pI6c",
				  dev->name, address);
	}

	socs_neigh_localnode_ip_down(dev->name, address, family);
	socs_mul_localnode_down(dev->name);
	return 0;
}

static int netdev_up(struct net_device *dev)
{
	log_debug("netdev_up,name %s", dev->name);
	socs_neigh_localnode_up(dev);
	socs_mul_localnode_up(dev->name);
	return 0;
}

void dev_up_timer_expire(unsigned long data)
{
	struct net_device *dev = NULL;
	log_debug("[dev_up_timer_expire] get %s's dev", ifname );
	dev = dev_get_by_name(&init_net, ifname);

	if (dev) {
		netdev_up(dev);
		dev_put(dev);
	} else {
		log_debug("[dev_up_timer_expire] can not get %s's dev", ifname );
	}
}

static int netdev_event(struct notifier_block *this, unsigned long event,
						void *ptr)
{
	struct net_device *dev = netdev_notifier_info_to_dev(ptr);

	if (dev->ifindex == LOOPBACK_IFINDEX) {
		return 0;
	}

	switch (event) {
	case NETDEV_DOWN:
		log_debug("[netdev_event]NETDEV_DOWN %s is stop running",
				  dev->name); /*ifdown eth */
		netdev_remove(dev);
		break;

	case NETDEV_CHANGE:
		if (netif_running(dev) && dev->ethtool_ops->get_link(dev)) {
			log_debug("[netdev_event]NETDEV_CHANGE %s is running",
					  dev->name); /*ifup eth  and insert the physical link*/
			memcpy(ifname, dev->name, IFNAMSIZ);
			//mod_timer(&dev_up_timer,jiffies+msecs_to_jiffies(3000));
			netdev_up(dev);
		} else {
			log_debug("[netdev_event]NETDEV_CHANGE %s is stop running",
					  dev->name); /*pull out the physical link*/
			netdev_remove(dev);
		}

		break;

	case NETDEV_UP:
		log_debug("[netdev_event]NETDEV_UP %s is up", dev->name);
		break;

	case NETDEV_CHANGEADDR:
		log_debug("[netdev_event]NETDEV_CHANGEADDR %s is up", dev->name);
		break;

	default:
		log_debug("[netdev_event]not the event I want,%s'event id is %ld", dev->name,
				  event);
		break;
	}

	return 0;
}

static int ipv4addr_event(struct notifier_block *this, unsigned long event,
						  void *ptr)
{
	struct in_ifaddr *ifa = NULL;
	struct net_device *dev = NULL;
	ifa = (struct in_ifaddr *)ptr;
	dev = ifa->ifa_dev->dev;

	if (dev->ifindex == LOOPBACK_IFINDEX) {
		log_debug("[ipv4addr_event]dev is loopback %s,inet addr:%pI4", dev->name,
				  &ifa->ifa_address);
		return NOTIFY_DONE;
	}

	switch (event) {
	case NETDEV_UP:
		log_debug("[ipv4addr_event]IP4_UP %s,inet addr:%pI4", dev->name,
				  &ifa->ifa_address);
		netdev_up(dev);
		break;

	case NETDEV_DOWN:
		log_debug("[ipv4addr_event]IP4_DOWN %s,inet addr:%pI4", dev->name,
				  &ifa->ifa_address);
		netdev_ip_remove(dev, &ifa->ifa_address, IPV4);
		break;

	default:
		log_debug("[ipv4addr_event]not the event I want,%s'event id is %ld,inet addr:%pI4",
				  dev->name, event, &ifa->ifa_address);
		break;
	}

	return NOTIFY_DONE;
	//return 0;
}


static int ipv6addr_event(struct notifier_block *this, unsigned long event,
						  void *ptr)
{
	struct inet6_ifaddr *ifa = NULL;
	struct net_device *dev = NULL;
	ifa = (struct inet6_ifaddr *)ptr;
	dev = ifa->idev->dev;

	if (dev->ifindex == LOOPBACK_IFINDEX) {
		log_debug("[ipv6addr_event]dev is loopback %s,inet addr:%pI6c", dev->name,
				  &ifa->addr);
		return NOTIFY_DONE;
	}

	switch (event) {
	case NETDEV_UP:
		log_debug("[ipv6addr_event]IP6_UP %s,inet addr:%pI6c", dev->name, &ifa->addr);

		if (ipv6_addr_type(&ifa->addr)&IPV6_ADDR_LINKLOCAL) {
			log_debug("[ipv6addr_event]inet addr:%pI6c is local", &ifa->addr);
		} else {
			netdev_up(dev);
		}

		break;

	case NETDEV_DOWN:
		log_debug("[ipv6addr_event]IP6_DOWN %s,inet addr:%pI6c", dev->name, &ifa->addr);

		if (ipv6_addr_type(&ifa->addr)&IPV6_ADDR_LINKLOCAL) {
			log_debug("[ipv6addr_event]inet addr:%pI6c is local", &ifa->addr);
		} else {
			netdev_ip_remove(dev, &ifa->addr, IPV6);
		}

		break;

	default:
		log_debug("[ipv6addr_event]not the event I want,%s'event id is %ld,inet addr:%pI6c",
				  dev->name, event, &ifa->addr);
		break;
	}

	return NOTIFY_DONE;
	//return 0;
}


static struct notifier_block netdev_down = {
	.notifier_call = netdev_event,
};

static struct notifier_block ipv4addr_change = {
	.notifier_call = ipv4addr_event,
};

static struct notifier_block ipv6addr_change = {
	.notifier_call = ipv6addr_event,
};

int  netdev_monitor_init(void)
{
	setup_timer(&dev_up_timer, dev_up_timer_expire, 0);
	register_inetaddr_notifier(&ipv4addr_change);
	register_inet6addr_notifier(&ipv6addr_change);
	return register_netdevice_notifier(&netdev_down);
}
void netdev_monitor_destroy(void)
{
	del_timer(&dev_up_timer);
	unregister_inetaddr_notifier(&ipv4addr_change);
	unregister_inet6addr_notifier(&ipv6addr_change);
	unregister_netdevice_notifier(&netdev_down);
}
